#!/usr/bin/env python3
#
# Wireshark - Network traffic analyzer
# By Gerald Combs <gerald@wireshark.org>
# Copyright 1998 Gerald Combs
#
# SPDX-License-Identifier: GPL-2.0-or-later
'''Update the BACNET vendors list.

generate-bacnet-vendors generates output containing BACNET vendor Identifiers.

Copyright 2023 Jaap Keuter <jaap.keuter@xs4all.nl>
based on work by Anish Bhatt <anish@chelsio.com>

'''

import sys
import urllib.request
import urllib.error
from bs4 import BeautifulSoup

def exit_msg(msg=None, status=1):
    if msg is not None:
        sys.stderr.write(msg + '\n\n')
    sys.stderr.write(__doc__ + '\n')
    sys.exit(status)

req_headers = { 'User-Agent': 'Wireshark generate-bacnet-vendors' }
url = "https://bacnet.org/assigned-vendor-ids/"

OUTPUT_FILE = "epan/dissectors/packet-bacnet-data.c"

MIN_VENDORS = 1500 # 1581 as of 2025-12-01

PROLOG = f'''/*
 * This file was generated by running ./tools/generate-bacnet-vendors.py
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * The BACNET Assigned Vendor IDs table is available at:
 * <{url}>
 */

#include <wsutil/value_string.h>

'''

def retrieve_vendors():
    try:
        req = urllib.request.Request(url, headers=req_headers)
        response = urllib.request.urlopen(req)
        lines = response.read().decode()
        response.close()
    except urllib.error.HTTPError as err:
        exit_msg("HTTP error fetching {0}: {1}".format(url, err.reason))
    except urllib.error.URLError as err:
        exit_msg("URL error fetching {0}: {1}".format(url, err.reason))
    except OSError as err:
        exit_msg("OS error fetching {0}: {1}".format(url, err.strerror))
    except Exception:
        exit_msg("Unexpected error:", sys.exc_info()[0])

    soup = BeautifulSoup(lines, "html.parser")
    table = soup.find('table')
    rows = table.find_all('tr')

    if len(rows) < MIN_VENDORS:
        exit_msg(f"Fewer vendors than expected: got {len(rows)} but expected at least {MIN_VENDORS}")

    vendors = ["static const value_string\n", "BACnetVendorIdentifiers[] = {\n"]

    for tr in rows:
        cols = tr.find_all('td')
        entry = ""
        for index,td in enumerate(cols[0:2]):
            text = ''.join(td.find(string=True))
            if index == 0:
                entry = "    { %4s" % text
            else:
                entry += ", \"%s\" },\n" % text.rstrip()
        if entry:
            vendors.append(entry)

    vendors.append("    { 0, NULL }\n")
    vendors.append("};\n")
    vendors.append("value_string_ext BACnetVendorIdentifiers_ext = VALUE_STRING_EXT_INIT(BACnetVendorIdentifiers);\n")
    return vendors

def main():
    vendors = retrieve_vendors()

    with open(OUTPUT_FILE, "w") as bacnet_vendors_f:
        bacnet_vendors_f.write(PROLOG)

        bacnet_vendors_f.writelines(vendors)

if __name__ == '__main__':
    main()
